// This is an open source non-commercial project. Dear PVS-Studio, please check it.
// PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com

// ReSharper disable CheckNamespace
// ReSharper disable ClassNeverInstantiated.Global
// ReSharper disable CommentTypo
// ReSharper disable IdentifierTypo
// ReSharper disable InconsistentNaming
// ReSharper disable StringLiteralTypo
// ReSharper disable UnusedParameter.Local

/*
 * Ars Magna project, http://arsmagna.ru
 */

#region Using directives

using System;
using System.Collections.Generic;
using System.Text;

using AM.Reporting.Data;
using AM.Reporting.Engine;
using AM.Reporting.Utils;

#endregion

#nullable enable

namespace AM.Reporting.Code
{
    /// <summary>
    /// This static class contains methods that may be used to find expressions embedded
    /// in the object's text.
    /// </summary>
    public static class CodeUtils
    {
        internal enum Language
        {
            Cs,
            Vb
        }

        private static bool isTypeSuffixesInitialized;
        private static Dictionary<Type, string[]> typeSuffixes;

        #region Private Methods

        private static bool TypeHasSuffix (Type type)
        {
            return typeSuffixes.ContainsKey (type);
        }

        private static string GetTypeSuffix (Type type, Language lang)
        {
            typeSuffixes.TryGetValue (type, out string[] suffix);
            return suffix[(int)lang];
        }

        private static void InitializeTypeSuffixes()
        {
            typeSuffixes = new Dictionary<Type, string[]>();

            //c# and vb type prefixes
            typeSuffixes.Add (typeof (float), new string[] { "F", "F" });
            typeSuffixes.Add (typeof (double), new string[] { "D", "R" });
            typeSuffixes.Add (typeof (uint), new string[] { "U", "" });
            typeSuffixes.Add (typeof (long), new string[] { "L", "L" });
            typeSuffixes.Add (typeof (ulong), new string[] { "UL", "UL" });
            typeSuffixes.Add (typeof (decimal), new string[] { "M", "D" });
            isTypeSuffixesInitialized = true;
        }

        // adjusts StartIndex to the next char after end of string. Returns true if string is correct.
        private static bool SkipString (FindTextArgs args)
        {
            if (args.Text[args.StartIndex] == '"')
            {
                args.StartIndex++;
            }
            else
            {
                return true;
            }

            while (args.StartIndex < args.Text.Length)
            {
                if (args.Text[args.StartIndex] == '"')
                {
                    if (args.Text[args.StartIndex - 1] != '\\')
                    {
                        args.StartIndex++;
                        return true;
                    }
                }

                args.StartIndex++;
            }

            return false;
        }

        // find matching open and close brackets starting from StartIndex. Takes strings into account.
        // Returns true if matching brackets found. Also returns FoundText with text inside brackets,
        // StartIndex pointing to the OpenBracket and EndIndex pointing to the next char after CloseBracket.
        private static bool FindMatchingBrackets (FindTextArgs args, bool skipLeadingStrings)
        {
            if (!skipLeadingStrings)
            {
                args.StartIndex = args.Text.IndexOf (args.OpenBracket, args.StartIndex);
                if (args.StartIndex == -1)
                {
                    return false;
                }
            }

            var saveStartIndex = 0;
            var brCount = 0;

            while (args.StartIndex < args.Text.Length)
            {
                if (!SkipString (args))
                {
                    return false;
                }

                if (args.StartIndex + args.OpenBracket.Length > args.Text.Length)
                {
                    return false;
                }

                if (args.Text.SubstringCompare (args.StartIndex, args.OpenBracket))
                {
                    if (brCount == 0)
                    {
                        saveStartIndex = args.StartIndex;
                    }

                    brCount++;
                }
                else if (args.Text.SubstringCompare (args.StartIndex, args.CloseBracket))
                {
                    brCount--;
                    if (brCount == 0)
                    {
                        args.EndIndex = args.StartIndex + args.CloseBracket.Length;
                        args.StartIndex = saveStartIndex;
                        args.FoundText = args.Text.Substring (args.StartIndex + args.OpenBracket.Length,
                            args.EndIndex - args.StartIndex - args.OpenBracket.Length - args.CloseBracket.Length);
                        return true;
                    }
                }

                args.StartIndex++;
            }

            return false;
        }

        #endregion

        #region Internal Methods

        // determines whether given index is inside brackets, or is after OpenBracket
        internal static bool IndexInsideBrackets (FindTextArgs args)
        {
            var pos = args.StartIndex;
            args.StartIndex = 0;

            while (args.StartIndex < pos)
            {
                // find open bracket
                args.StartIndex = args.Text.IndexOf (args.OpenBracket, args.StartIndex);
                if (args.StartIndex == -1)
                {
                    return false;
                }

                // missing close bracket
                if (!FindMatchingBrackets (args, false))
                {
                    return true;
                }

                // pos is inside brackets
                if (args.StartIndex < pos && args.EndIndex > pos)
                {
                    return true;
                }

                args.StartIndex = args.EndIndex;
            }

            return false;
        }

        internal static string GetOptionalParameter (System.Reflection.ParameterInfo par, Language lang)
        {
            if (!isTypeSuffixesInitialized)
            {
                InitializeTypeSuffixes();
            }

            var optionalParamString = " = ";
            if (par.DefaultValue.GetType().IsEnum)
            {
                optionalParamString += par.DefaultValue.GetType().Name + ".";
            }

            optionalParamString += par.DefaultValue.ToString();
            if (TypeHasSuffix (par.ParameterType))
            {
                optionalParamString += GetTypeSuffix (par.ParameterType, lang);
            }

            return optionalParamString;
        }

        internal static string FixExpressionWithBrackets (string expression)
        {
            var result = expression;
            if (expression.StartsWith ("[") && expression.EndsWith ("]"))
            {
                var tempExpression = expression.Substring (1, expression.Length - 2);
                var firstOpen = tempExpression.IndexOf ("[");
                var firstClose = tempExpression.IndexOf ("]");
                var lastOpen = tempExpression.LastIndexOf ("[");
                var lastClose = tempExpression.LastIndexOf ("]");
                if ((firstOpen < 0 && firstClose >= 0) || (lastOpen >= 0 && lastClose < 0)
                                                       || (firstOpen >= 0 && firstClose >= 0 && firstClose < firstOpen)
                                                       || (lastOpen >= 0 && lastClose >= 0 && lastOpen > lastClose))
                {
                    result = expression;
                }
                else
                {
                    result = tempExpression;
                }
            }
            else
            {
                result = expression;
            }

            return result;
        }

        #endregion

        /// <summary>
        /// Returns expressions found in the text.
        /// </summary>
        /// <param name="text">Text that may contain expressions.</param>
        /// <param name="openBracket">The char sequence used to find the start of expression.</param>
        /// <param name="closeBracket">The char sequence used to find the end of expression.</param>
        /// <returns>Array of expressions if found; otherwise return an empty array.</returns>
        public static string[] GetExpressions (string text, string openBracket, string closeBracket)
        {
            List<string> expressions = new List<string>();
            var args = new FindTextArgs
            {
                Text = new FastString (text),
                OpenBracket = openBracket,
                CloseBracket = closeBracket
            };

            while (args.StartIndex < args.Text.Length) //text.Length
            {
                if (!FindMatchingBrackets (args, false))
                {
                    break;
                }

                expressions.Add (args.FoundText);
                args.StartIndex = args.EndIndex;
            }

            return expressions.ToArray();
        }

        /// <summary>
        /// Gets first expression found in the text.
        /// </summary>
        /// <param name="args">Object with find arguments.</param>
        /// <param name="skipStrings">Indicates whether to skip strings.</param>
        /// <returns>The expression if found; otherwise, returns null.</returns>
        public static string GetExpression (FindTextArgs args, bool skipStrings)
        {
            if (args.StartIndex < args.Text.Length)
            {
                if (FindMatchingBrackets (args, skipStrings))
                {
                    return args.FoundText;
                }
            }

            return null;
        }
    }
}
